#  library.py - Media library abstraction module
#  Copyright (C) 2005-2007 Sham Chukoury <eleusis@xmms.org>
#
#  This library is free software; you can redistribute it and/or
#  modify it under the terms of the GNU Lesser General Public
#  License as published by the Free Software Foundation; either
#  version 2.1 of the License, or (at your option) any later version.
#
#  This library is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#  Lesser General Public License for more details.

from Chalyx.base import ChalyxObj
from Chalyx.config import Config
from sepal import Sepal
import time
import heapq
import re

class CachedItem:
	def __init__(self, data):
		self.data = data
		self.hits = -1
		self.lastAccess = 0
		self.hit()
	
	def hit(self):
		# wtf are we really doing with the time value here?
		self.hits += 1
		self.lastAccess = int(time.time() % 3600)
	
	def score(self):
		return self.hits + self.lastAccess
	
	def __cmp__(self, other):
		return self.score() - other.score()
	
	def __hash__(self):
		return hash(self.data["id"])

class LibCache(ChalyxObj):
	def __init__(self, cfghandler):
		ChalyxObj.__init__(self, cfgHandler, "LibCache")
		self.items = {}
		self.reverseMap = {}
		self.scoreHeap = []
		self.cfg = Config()
		self.cfg.register("cachesize", "Library cache size", 100)
		cfgHandler["LibCache"] = self.cfg
		self.cacheSize = int(self.cfg["cachesize"])
	
	def is_cached(self, id):
		return self.items.has_key(id)
	
	def add(self, id, data):
		item = CachedItem(data)
		self.items[id] = item
		self.reverseMap[item] = id
		heapq.heappush(self.scoreHeap, item)
		self.manage_size()
	
	def get(self, id):
		item = self.items[id]
		item.hit()
		return item
	
	def manage_size(self):
		while len(self.items) >= self.cacheSize:
			evictee = heapq.heappop(self.scoreHeap)
			del self.items[self.reverseMap[evictee]]
			del self.reverseMap[evictee]

class Library(Sepal, ChalyxObj):
	def __init__(self, service):
		Sepal.__init__(self, service)
		ChalyxObj.__init__(self, service.cfgHandler, "Library")
		self.cfg = Config()
		self.cfg.register("cache-enabled", "Enable library cache", False)
		self.cfgHandler["Library"] = self.cfg
		# TODO: evict cached items as they're removed from playlist
		# possibly have a slot run on 'playlist-change'
		self.cache = None
		if self.cfg["cache-enabled"]:
			self.cache = LibCache(self.cfgHandler)
		self.__resQ = {}
	
	def item_info(self, libID, cb, udata = None):
		def __info_cb(data):
			if self.cache:
				self.cache.add(libID, data)
			for c in self.__resQ[libID]:
				c[0](data, c[1])
			del self.__resQ[libID]
		if not self.cfg["cache-enabled"] or not self.cache.is_cached(libID):
			# check if this ID is already being resolved
			if not self.__resQ.has_key(libID):
				# (miss)
				self.__resQ[libID] = [(cb, udata)]
				self.service.library_get_info(libID, __info_cb)
			else:
				# info request for this ID is already pending (pre_hit)
				self.__resQ[libID].append((cb, udata))
		else:
			# (hit)
			item = self.cache.get(libID)
			cb(item.data, udata)
	
	def find(self, query, cb, udata=None):
		def __find_cb(data):
			cb(data, udata)
		self.service.library_find(query, __find_cb)
